import dayjs from "dayjs";
import store from "@/store";
import config from "@/config";
import globalMethods from "@/GlobalMethodProvider";

import fso from "@/utils/FileOperate/operate";
import sm from "@/utils/ScheduleManage/operate";
import getDeviceInfo from "@/utils/getDeviceInfo";
import stateCodeMap from "@/utils/websocket/stateCodeMap";
import { gotoVideoDisplayPage } from "@/utils";
import { setNativeDate } from "@/utils/date";
import { sendData as websocketSendData } from "@/utils/websocket/index";
import { fetchEntryNetworkInfo, getEntryNetworkInfo, setEntryNetworkInfo } from "@/utils/entryNetworkInfoOperate";
import { Module, AudioManage, LuminanceManage } from "@/utils/nativeModules";
import { MaterialSqliteType, ScheduleSqliteType, materialSqlh, scheduleSqlh } from "@/sqlite/exports";

import { uploadData } from "@/api/websocket";

import type { StateCodeMapKey } from "@/utils/websocket/stateCodeMap";

export type HandleWSCmdParams = {
  /** 状态码 */
  cmd: StateCodeMapKey;

  /** 消息 */
  msg: string;

  /** 命令数据 */
  data: {
    cmdCode: number;
    batchId: string;
    [k: string]: any;
  };
};

/**
 * 统一处理 websocket 命令
 */
export default (opt: HandleWSCmdParams) => {
  // 操作响应
  return new Promise<void | Error>(async reolve => {
    fso.info(`接收到消息: `, opt);
    try {
      if (handleCmdMethod[opt.cmd]) {
        const res = await handleCmdMethod[opt.cmd](opt);

        if (res) {
          fso.warn("命令执行失败: ", res.message);
          reolve(res);
        } else {
          fso.info("命令执行成功");

          // TODO 离线指令回传数据
          if (opt?.data?.cmdCode) {
            // 这些命令是有上传数据的排除掉
            const flag = [
              stateCodeMap.CAPTURE,
              stateCodeMap.DEVICE_INFO,
              stateCodeMap.SCHEDUL_INFO,
              stateCodeMap.UPLOAD_LOG
              // @ts-ignore
            ].includes(opt.data.cmdCode);

            // 不是这些命令的就要回传数据
            if (!flag) {
              const code = await getEntryNetworkCode();
              // 这里尽量回传数据, 如果是重启指令是不会执行到这里的
              // 查询离线指令时不会查询重启, 日志等相关的实时性命令
              if (code) {
                // 这里要延迟回传数据, 后台需要在发送指令后需要把数据插入后这里才能正确的修改状态
                setTimeout(() => {
                  uploadData({
                    batchId: opt.data?.batchId || "",
                    deviceCode: code,
                    cmd: String(opt.data.cmdCode),
                    data: JSON.stringify({
                      ...(opt.data || {}),
                      cmd: opt.data.cmdCode,
                      backTime: Date.now() // 返回数据时间戳
                    })
                  })
                    .then(res => {
                      fso.info("数据回传响应: ", res.data);
                    })
                    .catch(err => {
                      fso.error("数据回传失败", err);
                    });
                }, 500);
              } else {
                fso.error("没有获取到设备编号");
              }
            } else {
              console.log("=== 不用回传数据 ===");
            }
          }
          reolve();
        }
      } else {
        const tip = `未知的命令码 ${opt.cmd}`;
        fso.warn(`${tip} -> `, opt);
        reolve(new Error(tip));
      }
    } catch (err) {
      fso.warn("命令执行失败: ", err);
      if (err instanceof Error) {
        reolve(err);
      } else {
        reolve(new Error(`${err}`));
      }
    }
  });
};

/** 命令处理方法 */
export const handleCmdMethod: { [k in StateCodeMapKey]: (opt: HandleWSCmdParams) => Promise<void | Error> } = {
  /** 测试命令 */
  [stateCodeMap.TEST_CMD]: opt => {
    console.log("测试命令: ", opt);
    return Promise.resolve();
  },
  /** 连接成功 */
  [stateCodeMap.CONNECT]: opt => {
    fso.info("websocket 后台连接成功");
    websocketSendData(config.webSocketHeartbeatData);
    if (config.IS_DEBUG) {
      console.log("发送一次心跳");
    }
    return Promise.resolve();
  },
  /** 认证通过 */
  [stateCodeMap.DEVICE_AUTH]: opt => {
    fso.info("设备通过认证 :", opt.data);
    getEntryNetworkInfo()
      .then(res => {
        if (res) {
          // 修改设备的状态未已入网
          return setEntryNetworkInfo({
            ...res,
            status: "0"
          });
        } else {
          return Promise.resolve(false);
        }
      })
      .then(() => {
        fso.info("本地入网数据状态修改成功");
      })
      .catch(err => {
        fso.warn("本地入网数据状态修改失败", err);
      })
      .finally(() => {
        gotoVideoDisplayPage();
      });
    return Promise.resolve();
  },
  /** 获取排期和素材信息 */
  [stateCodeMap.SCHEDUL_INFO]: opt => {
    return new Promise(async resolve => {
      fso.info("获取排期和素材信息 :", opt.data);

      const code = await getEntryNetworkCode();
      if (code) {
        if (!scheduleSqlh) {
          resolve(new Error("排期数据库初始失败"));
          return;
        }

        if (!materialSqlh) {
          resolve(new Error("素材数据库初始失败"));
          return;
        }

        const scheduleList = await scheduleSqlh.getList<ScheduleSqliteType[]>();
        fso.info("排期数据: ", scheduleList);

        const materialList = await materialSqlh.getList<MaterialSqliteType[]>();
        fso.info("素材数据: ", materialList);

        /** 上传的数据 */
        const data = {
          scheduleList: scheduleList.map(e => {
            return {
              ...e,
              id: e.sourceId
            };
          }),
          materialList: materialList.map(e => {
            return {
              id: e.sourceId,
              code: e.code,
              type: e.type,
              resolution: e.resolution,
              officeCode: e.officeCode,
              officeName: e.officeName,
              startTime: e.startTime,
              endTime: e.endTime,
              name: e.name,
              file: e.file,
              parentCode: e.parentCode,
              isDown: e.isDown,
              downFileToNativePath: e.downFileToNativePath,
              downFileUrl: e.downFileUrl
            };
          })
        };

        await uploadData({
          batchId: opt.data?.batchId || "",
          deviceCode: code,
          cmd: String(opt.data.cmdCode),
          data: JSON.stringify({
            ...(data || {}),
            cmd: opt.data.cmdCode,
            backTime: Date.now() // 返回数据时间戳
          }),
        });
        resolve();
      }
      resolve();
    });
  },
  /** 同步排期 */
  [stateCodeMap.SYNC_SCHEDUL]: async opt => {
    fso.info("同步排期 :", opt.data);
    sm.cancelDownMaterial();
    sm.syncSchedule()
      .then(flag => {
        if (!flag) fso.error("排期数据同步失败");
      })
      .catch(err => {
        console.log("err: ", err);
      });
  },
  /** 应用版本升级 */
  [stateCodeMap.UPDATE_APP]: opt => {
    return new Promise(async resolve => {
      try {
        sm.cancelDownMaterial();
        fso.info("应用版本升级 :", opt.data);
        // 下载url
        const updateVersionUrl = opt.data?.updateVersionUrl;
        // 下载分片
        const updateVersionIsChunk = opt.data?.updateVersionIsChunk === "1" ? false : true;
        // 下载文件的大小
        const updateVersionFileSize = Number(opt.data?.updateVersionFileSize || "-1");
        // 下载分片
        const updateVersionMethod: "GET" | "POST" = (opt.data?.updateVersionMethod as any) || "GET";

        if (updateVersionUrl) {
          globalMethods.$showMessage({
            type: "toast",
            message: "正在下载多媒体升级安装包"
          });

          // 获取app信息
          const appInfo = await Module.getAppInfo();
          fso.info("APP信息 :", appInfo);

          const apksDirExists = fso.exists(fso.apksDir);
          if (!apksDirExists) await fso.mkdirDir(fso.apksDir);
          const downFileToNativePath = `${fso.apksDir}/${Date.now()}.apk`;

          // 删除该目录下面的所有的APK文件
          const dirs = await fso.readDir(fso.apksDir);
          for (let i = 0; i < dirs.length; i++) {
            const item = dirs[i];
            if (item.path.endsWith(".apk")) {
              fso.info("删除旧的安装包: ", item.path);
              await fso.rm(item.path);
            }
          }

          let filePath;
          if (updateVersionIsChunk) {
            if (updateVersionFileSize && updateVersionFileSize !== -1) {
              fso.info("分片下载APP");
              filePath = await fso.sliceDownload({
                url: updateVersionUrl,
                method: updateVersionMethod,
                fileSize: updateVersionFileSize,
                rangeSize: config.downMaterialChunkSize,
                restDownload: true,
                downloadFilePath: downFileToNativePath,
                onSomeRestDownload(fileList) {
                  fso.info("本地存在还有未下载完成的切片文件", fileList);
                },
                onCurrentRestDownload(fileList) {
                  fso.info(`要下载的 ${this.downloadFilePath} 有未下载成功的部分, 继续下载剩余的部分`);
                },
                onSliceFileDownError(fileInfo) {
                  fso.info(`${fileInfo.targetFilePath} 下载失败`);
                },
                onBeforeSliceDownFile(opt) {
                  // fso.info("开始下载: ", opt);
                },
                onAfterSliceDownFile(opt) {
                  fso.info("下载完成: ", opt);
                },
                onUnfinishFileDown(opt) {
                  fso.info(`分片文件未下载完成`, opt);
                },
                onBuildSliceFile(opt) {
                  fso.info(`下载完成, 开始合并分片文件`, opt);
                }
              });
            } else {
              fso.info("分片下载APP缺少 updateVersionFileSize 字段, 使用普通下载");
              filePath = await fso.downFile({
                method: updateVersionMethod,
                url: updateVersionUrl,
                downFileToNativePath
              });
            }
          } else {
            fso.info("普通下载APP");
            filePath = await fso.downFile({
              method: updateVersionMethod,
              url: updateVersionUrl,
              downFileToNativePath
            });
          }
          fso.info("APP下载到 :", filePath);
          if (typeof filePath === "string") {
            // TODO 校验安装包是否是完整的
            const isRoot = await Module.isRoot();
            if (isRoot) {
              const res2 = await Module.execShell(`pm install -r ${filePath}`, true);
              if (config.IS_DEBUG) {
                console.log("APP安装响应: ", res2);
              }
              const error = new Error(`APP安装失败: filePath: ${filePath}, message: ${res2?.stderr}`);
              if (res2) {
                if (res2.exitValue === 0) {
                  fso.info("APP安装响应: ", res2.stdout);
                  resolve();
                } else {
                  fso.error("APP安装失败: ", error);
                  resolve(error);
                }
              } else {
                resolve(error);
              }
            } else {
              await Module.actionViewIntent(filePath, "apk");
              // TODO 这里不能删除安装包, 否则会导致安装失败
            }
          } else {
            resolve(new Error("APP下载失败: " + filePath));
          }
        } else {
          resolve(new Error("没有获取到下载链接字段 updateVersionUrl"));
        }
      } catch (err) {
        resolve(new Error("版本升级失败: " + err));
      }
    });
  },
  /** 重启 */
  [stateCodeMap.RESTART]: opt => {
    sm.cancelDownMaterial();
    fso.info("重启指令 type(1: 重启系统, 2: 重启APP):", opt.data);
    setTimeout(() => {
      if (opt.data?.type === "2") {
        // TODO 这里会导致应用没办法跳转到播放页面
        // Module.restartApp();

        // 使用 shell 命令重启
        Module.getAppInfo()
          .then(res => res)
          .then(appInfo => {
            if (appInfo) {
              // TODO 没有权限执行
              Module.execShell(
                `am force-stop ${appInfo.packageName} && am start -n ${appInfo.packageName}/.MainActivity`,
                true
              ).then(res => {
                if (res?.exitValue !== 0) {
                  fso.warn("App重启失败", res?.stderr);
                }
              });
            } else {
              return new Error("没有获取App信息");
            }
          })
          .catch(err => {
            fso.warn("App重启失败", err);
          });
      } else {
        Module.reboot();
      }
    }, 300);
    return Promise.resolve();
  },
  /** 调节音量 */
  [stateCodeMap.SET_VOLUME]: opt => {
    return new Promise(resolve => {
      fso.info("调节音量 :", opt.data);
      const volume = Number(opt.data?.volume || "50");
      AudioManage.setCurrentVolume(volume)
        .then(err => {
          if (err) {
            resolve(new Error(`调节音量失败 ${err} ${opt.data}`));
          } else {
            resolve();
          }
        })
        .catch(err => {
          resolve(new Error(`${err}`));
        });
    });
  },
  /** 调节亮度 */
  [stateCodeMap.SET_BRIGHTNESS]: async opt => {
    return new Promise(resolve => {
      fso.info("调节亮度 :", opt.data);
      const brightness = Number(opt.data?.brightness || "50");
      LuminanceManage.setLuminance(brightness)
        .then(err => {
          if (err) {
            resolve(new Error(`调节亮度失败 ${err} ${opt.data}`));
          } else {
            resolve();
          }
        })
        .catch(err => {
          resolve(new Error(`${err}`));
        });
    });
  },
  /** 截屏 */
  [stateCodeMap.CAPTURE]: opt => {
    return new Promise(async resolve => {
      try {
        fso.info("截图 :", opt.data);
        const code = await getEntryNetworkCode();
        if (code) {
          const captureScreen = await fso.captureScreen();
          if (captureScreen instanceof Error) {
            resolve(captureScreen);
            return;
          }

          fso
            .uploadFile({
              url: config.serveUrl.getUploadUrl(),
              method: "POST",
              formDataKey: "file",
              fileName: captureScreen.fileName,
              filePath: captureScreen.filePath,
              otherFormData: [
                { name: "batchId", data: opt.data?.batchId || "" },
                { name: "deviceCode", data: code },
                { name: "data", data: "" },
                { name: "cmd", data: String(opt.data.cmdCode) }
              ]
            })
            .then(uploadFileRes => {
              fso.info("截图上传响应: ", uploadFileRes.data);
              resolve();
            })
            .catch(err => {
              resolve(new Error("截图上传内部报错: " + err));
            })
            .finally(() => {
              fso.info("删除截图文件: ", captureScreen.filePath);
              fso.rm(captureScreen.filePath);
            });
        } else {
          resolve(new Error("设备 code 获取失败"));
        }
      } catch (err) {
        // TODO
        // console.log("全局报错: ", err);
        resolve(new Error("截图上传全局失败: " + err));
      }
    });
  },
  /** 获取设备信息 */
  [stateCodeMap.DEVICE_INFO]: opt => {
    return new Promise(async resolve => {
      try {
        fso.info("获取设备信息 :", opt.data);
        const code = await getEntryNetworkCode();
        if (code) {
          const deviceInfo = await getDeviceInfo();
          const appInfo = await Module.getAppInfo();
          const isRoot = await Module.isRoot();
          const pixelInfo = await Module.getPixel();
          const entryNetworkInfo = await getEntryNetworkInfo();

          const updata = {
            ...deviceInfo,
            ...appInfo,
            isRoot,
            pixel: `${pixelInfo?.width}*${pixelInfo?.height}`,
            entryNetworkInfo
          };
          fso.info("设备信息 :", updata);

          await uploadData({
            batchId: opt.data?.batchId || "",
            deviceCode: code,
            cmd: String(opt.data.cmdCode),
            data: JSON.stringify({
              ...(updata || {}),
              cmd: opt.data.cmdCode,
              backTime: Date.now() // 返回数据时间戳
            })
          });
          resolve();
        }
      } catch (err) {
        resolve(new Error("获取设备信息失败: " + err));
      }
    });
  },
  /** 上传日志文件 */
  [stateCodeMap.UPLOAD_LOG]: async opt => {
    return new Promise(async resolve => {
      try {
        fso.info("上传日志文件 :", opt.data);
        const code = await getEntryNetworkCode();
        if (code) {
          const logDate = opt.data?.logDate;
          const fileName = dayjs(logDate).format(`YYYY_MM_DD.log`);
          const filePath = `${fso.logDir}/${fileName}`;

          const exists = await fso.exists(filePath);
          if (exists) {
            fso
              .uploadFile({
                url: config.serveUrl.getUploadUrl(),
                method: "POST",
                formDataKey: "file",
                fileName: `${code}_${fileName}`,
                filePath,
                otherFormData: [
                  { name: "batchId", data: opt.data?.batchId || "" },
                  { name: "deviceCode", data: code },
                  { name: "data", data: "" },
                  { name: "cmd", data: String(opt.data.cmdCode) }
                ]
              })
              .then(uploadFileRes => {
                fso.info("日志上传响应: ", uploadFileRes.data);
                resolve();
              })
              .catch(err => {
                resolve(new Error("日志上传内部失败: " + err));
              });
          } else {
            resolve(new Error("日志上传失败, 文件不存在: " + filePath));
          }
        }
      } catch (err) {
        // TODO
        // console.log("全局报错: ", err);
        resolve(new Error("日志上传全局失败: " + err));
      }
    });
  },
  /** 设置系统时间 */
  [stateCodeMap.SET_SYS_TIME]: opt => {
    return new Promise(resolve => {
      console.log("设置系统时间: ", opt);
      const sysTime = opt.data?.sysTime;
      if (sysTime) {
        setNativeDate(sysTime)
          .then(() => {
            resolve();
          })
          .catch(err => {
            resolve(new Error(`${err}`));
          });
      } else {
        resolve(new Error("没有获取到系统时间字段 sysTime"));
      }
    });
  },
  /** 清理素材 */
  [stateCodeMap.REMOVE_RESOURCE]: opt => {
    sm.cancelDownMaterial();
    return new Promise(async resolve => {
      try {
        fso.info("清理素材 :", opt.data);
        let filePaths: string[] | string = opt.data?.filePaths;
        if (typeof filePaths === "string") filePaths = filePaths.split(",");

        if (Array.isArray(filePaths)) {
          if (filePaths.length === 0) {
            resolve(new Error("filePaths 为空"));
            return;
          }

          for (let i = 0; i < filePaths.length; i++) {
            const filePath = filePaths[i];
            const exists = await fso.exists(filePath);
            if (exists) {
              const stat = await fso.stat(filePath);
              if (stat.isDirectory()) {
                resolve(new Error(`该文件路径是目录不允许删除: ${filePath}`));
              } else {
                await fso.rm(filePath);
                await fso.info(`删除文件成功: ${filePath}`);
                // 如果删除的资源是当前播放的话则重新查找素材播放
                if (store.getState().index.curPlayData.filePath === filePath) {
                  sm.playPolyfill();
                }
              }
            } else {
              await fso.warn(`该路径不存在: ${filePath}`);
            }
          }

          sm.loopSeek();
          resolve();
        } else {
          sm.stopSeek();
          fso.info(`没有获取到要删除的资源路径, 删除所有的素材`);
          // const res = await sm.clearOldAppNativeData();
          // fso.info(`旧版APP素材清理: ${res ? "成功" : "失败"}`);
          const items = await sm.getDownloadedMaterial();
          for (const item of items) {
            if (store.getState().index.curPlayData.filePath === item.path) {
              sm.playPolyfill();
            }
            await fso.rm(item.path);
            if (materialSqlh) {
              // 数据库数据更新为未下载
              await materialSqlh.updateByCustom<MaterialSqliteType>(
                { isDown: "1" },
                { downFileToNativePath: item.path }
              );
            }
          }
          sm.loopSeek();
          resolve();
        }
      } catch (err) {
        sm.loopSeek();
        resolve(new Error("删除文件出错了: " + err));
      }
    });
  },
  /** 设置设备编号 */
  [stateCodeMap.SET_DEVICE_CODE]: opt => {
    fso.info("设置设备编号 :", opt.data);
    const deviceCode = opt.data?.deviceCode;
    if (deviceCode) {
      fetchEntryNetworkInfo(deviceCode);
    } else {
      fso.warn("设备编号数据获取失败: deviceCode");
    }
    return Promise.resolve();
  },
  [stateCodeMap.CLEAR_OLD_APP_RESOURCE]: async opt => {
    try {
      console.log("清理旧版APP素材文件: ", opt);
      await sm.clearOldAppNativeData();
    } catch (err) {
      fso.error("清理旧版APP素材文件失败", err);
    }

    return Promise.resolve();
  }
};

// 获取入网的 code
const getEntryNetworkCode = () => {
  return new Promise<string>(async resolve => {
    try {
      let code = store.getState().index.entryNetworkInfo?.code || "";
      if (!code) {
        const res = await getEntryNetworkInfo();
        code = res?.code || "";
      }
      if (!code) fso.warn("设备code获取失败");
      resolve(code);
    } catch (err) {
      console.log("err: ", err);
    }
  });
};
